
// Copyright (C) 2007 Richard Uhler
// This file is part of Esiply.
//
// Esiply is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License, Version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <assert.h>
//#include <gc_allocator.h>
#include <set>

#include "Boolean.h"
#include "Completion.h"
#include "Expression.h"
#include "ForInStatement.h"
#include "NameValue.h"
#include "Null.h"
#include "Object.h"
#include "Scope.h"
#include "String.h"
#include "UnaryExpression.h"
#include "Value.h"

ForInStatement::ForInStatement(NameValue* nv, Expression* lhs, Expression* rhs, Statement* stmt)
    : m_nv(nv), m_lhs(lhs), m_rhs(rhs), m_stmt(stmt)
{
    assert(m_nv == NULL || m_lhs == NULL);
    assert(m_nv || m_lhs);

    if (m_nv)
    {
        m_lhs = new UnaryExpression(&UnaryExpression::identifier,
                m_nv->get_name());
    }
}

Value* ForInStatement::execute_internal(Scope* scope) const
{

    // Ecma 12.6.4
    // 0. Evaluate nv
    if (m_nv)
    {
        Value* id = scope->lookup(m_nv->get_name());
        Expression* exp = m_nv->get_value();
        if (exp)
        {
            // 2. Evaluate initializer
            // 3. Call getValue on initer
            Value* initer = exp->evaluate(scope)->getValue();
            CHECK_EXCEPTION(initer);

            // 4. Call putValue(id, initer)
            id->putValue(initer);
        }
    }

    // 1. Evaluate rhs
    // 2. Call getValue on that
    // 3. Call toobject on that
    Object* obj = m_rhs->evaluate(scope)->getValue()->toObject();

    // 4. let v be empty
    Value* v = Null::null;

    // Get the set of enumerable properties of obj
    std::set<String*, ltstring > enumerable;
    obj->get_enumerable(enumerable);

    // For each of those...
    for (std::set<String*, ltstring>::iterator iter = enumerable.begin(); iter != enumerable.end(); iter++)
    {
        // 5. Get the name of the next property that doesn't have th
        // DontEnum attribute.
        // (And make sure it still is a property of the object
        String* name = *iter;
        if (!(obj->hasProperty(name)->isTrue()))
        {
            continue;
        }

        // 6. Evaluate the lhs.
        Value* lhs = m_lhs->evaluate(scope);
        CHECK_EXCEPTION(lhs);

        // 7. Call putvalue
        lhs->putValue(name);

        // 8. Evaluate statement
        Value* stmt = m_stmt->execute(scope);

        // 9. If stmt.value is not empty, let v = stmt.value
        // TODO: how do we know if it's empty?
        v = stmt->completion()->value();

        // 10. if break and target is in current label set
        // return (normal, v, empty). We don't have to do anything,
        // because Statement already takes care of it for us.

        // 11. If Continue and target is in current label set, continue
        if (stmt->completion()->type() == CT_CONTINUE
                && this->hasLabel(stmt->completion()->target()))
        {
            continue;
        }

        // 12. If abrupt(not normal), return it
        if (stmt->completion()->type() != CT_NORMAL)
        {
            return stmt;
        }

        // 13. go to step 5
    }

    // 14. Return (normal, v, empty)
    return new Completion(CT_NORMAL, v, Completion::empty);
}

void ForInStatement::getDeclaredVariables(std::vector<String* >& ids) const
{
    m_stmt->getDeclaredVariables(ids);
}

